package com.sdk.es.sdk.api.base;


import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.TypeReference;
import com.sdk.es.sdk.entity.common.*;
import com.sdk.es.sdk.utils.EsStrUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author wst
 * @date 2024/2/21
 * 基础接口请求，参考 ES 服务 EsBaseController
 */
@Slf4j
public abstract class EsBaseHttpManager<E> {

    private final static Logger logger = LoggerFactory.getLogger("esRequestLog");

    protected Class<E> eClazz;

    @Value("${esRequest.timeout:15000}")
    private Integer requestTimeout = 15000;

    /**
     * 地址前缀，需要在子类复写
     */
    protected abstract String getUrl();

    public Class<E> geteClazz() {
        if (null == this.eClazz) {
            try {
                this.eClazz = (Class<E>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
                log.debug("{} eClazz init:{}", this.getClass().getSimpleName(), this.eClazz.getSimpleName());
            } catch (Exception e) {
                log.error("获取参数实体类型异常：{}", e.getMessage(), e);
            }
        }
        return this.eClazz;
    }
    /**
     * 查询结果是否需要进行实体类转换
     *
     * @param result 查询结果
     * @return true：要转换
     */
    private boolean isConvert(EsResult result) {
        return null != result && result.isOk() && null != result.getData() && null != this.geteClazz();
    }

    /**
     * 查询结果实体类转换
     *
     * @param result 待转换实体
     */
    private void dataConvert(EsResult<E> result) {
        if (Boolean.TRUE.equals(this.isConvert(result))) {
            // 做一次实体类型转换
            E data = JSONObject.parseObject(JSONObject.toJSONString(result.getData()),this.geteClazz());
            result.setData(data);
        }
    }

    /**
     * 查询结果实体数组类转换
     *
     * @param result 待转换实体数组
     */
    private void listConvert(EsResult<List<E>> result) {
        if (Boolean.TRUE.equals(this.isConvert(result)) && !result.getData().isEmpty()) {
            // 做一次实体类型转换
            List<E> data = new ArrayList<>();
            for(E e : result.getData()){
                data.add(JSONObject.parseObject(JSONObject.toJSONString(e),this.geteClazz()));
            }
            result.setData(data);
        }
    }
    /**
     * 请求日志记录
     *
     * @param url      请求地址
     * @param httpCode 请求响应码
     * @param costTime 请求耗时
     * @param body     请求参数
     * @param resultString   请求结果
     * @param errMsg   异常信息
     */
    private void logAccess(String url, int httpCode, long costTime, String body, String resultString, String errMsg) {
        try {
            Map<String, Object> map = new HashMap<>(10);
            map.put("time", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss SSS")));
            map.put("url", url);
            map.put("httpCode", httpCode);
            map.put("costTime", costTime);
            map.put("header", "");
            map.put("body", body);
            if (EsStrUtils.isBlank(resultString)) {
                map.put("code", -1);
                map.put("msg", "操作失败!");
            } else {
                EsResult esResult = JSONObject.parseObject(resultString, EsResult.class);
                map.put("code", esResult.getCode());
                map.put("msg", esResult.getMsg());
//            map.put("data", result.getData());
            }
            if (!EsStrUtils.isBlank(errMsg)) {
                map.put("errMsg", errMsg);
            }
            logger.info(JSONObject.toJSONString(map));
        } catch (Exception e) {
            log.error("请求日志输出异常：{}", e.getMessage(), e);
        }
    }


    public <T> T postBody(String url,Object object,Type type) {
        String body = JSONObject.toJSONString(object);
        long startTime = System.currentTimeMillis();
        // 创建Httpclient对象
        HttpClient httpClient = HttpClients.createDefault();
        HttpResponse response = null;
        String errMsg = null;
        Integer httpCode = null;
        String resultString = "";
        try {
            url = getUrl() + url;
            // 创建Http Post请求
            HttpPost httpPost = new HttpPost(url);
            httpPost.setHeader("Content-Type", "application/json");
            httpPost.setHeader("innerReq", "innerReq");
            // 设置请求配置，包括超时时间
            RequestConfig requestConfig = RequestConfig.custom()
                    .setConnectTimeout(requestTimeout) // 连接超时5秒
                    .setSocketTimeout(10000)  // 数据读取超时10秒
                    .setConnectionRequestTimeout(5000) // 连接不够用的等待时间
                    .build();
            httpPost.setConfig(requestConfig);
            // 创建请求内容
            StringEntity entity = new StringEntity(body, "utf-8");
            entity.setContentType(ContentType.APPLICATION_JSON.toString());
            httpPost.setEntity(entity);
            // 执行http请求
            response = httpClient.execute(httpPost);
            httpCode = response.getStatusLine().getStatusCode();
            if(httpCode != 200){
                resultString = EsResult.failStr("请求失败码:"+httpCode);
            }else {
                resultString = EntityUtils.toString(response.getEntity(), "utf-8");
                return JSONObject.parseObject(resultString, type);
            }
        } catch (Exception e) {
            errMsg = e.getMessage();
            httpCode = -1;
            resultString = EsResult.failStr("出现未知异常");
        } finally {
            long endTime = System.currentTimeMillis();
            long costTime = endTime - startTime;
            logAccess(url, httpCode, costTime, body, resultString, errMsg);
        }
        return JSONObject.parseObject(resultString, type);
    }

    /**
     * 单条记录新增
     *
     * @param req 新增数据实体
     * @return 新增数据实体，回写 id、indexName
     */
    public EsResult<E> save(E req) {
        Type type = new TypeReference<EsResult<E>>() {}.getType();
        EsResult<E> result = postBody("/save", req, type);
        this.dataConvert(result);
        return result;
    }

    /**
     * 单条记录新增-异步
     *
     * @param req 新增数据实体
     * @return 返回成功
     */
    public EsResult saveAsync(E req) {
        return postBody("/saveAsync", req, EsResult.class);
    }

    /**
     * 多条记录新增
     *
     * @param list 新增数据实体数组
     * @return 新增数据实体数组，回写 id、indexName
     */
    public EsResult<List<E>> saveBatch(List<E> list) {
        Type type = new TypeReference<EsResult<List<E>>>(){}.getType();
        EsResult<List<E>> result = postBody("/saveBatch", list, type);
        this.listConvert(result);
        return result;
    }

    /**
     * 多条记录新增-异步
     *
     * @param list 新增数据实体数组
     * @return 新增数据实体数组，回写 id、indexName
     */
    public EsResult saveBatchAsync(List<E> list) {
        return postBody("/saveBatchAsync", list, EsResult.class);
    }

    /**
     * 根据 ID 更新数据
     *
     * @param req 修改数据实体
     * @return 是否成功
     */
    public EsResult update(E req) {
        return postBody("/update", req, EsResult.class);
    }

    /**
     * 根据 ID 更新数据-异步
     *
     * @param req 修改数据实体
     * @return 返回成功
     */
    public EsResult updateAsync(E req) {
        return postBody("/updateAsync", req, EsResult.class);
    }

    /**
     * 根据 ID 删除
     *
     * @param req 删除数据实体
     * @return 是否成功
     */
    public EsResult delete(E req) {
        return postBody("/delete", req, EsResult.class);
    }

    /**
     * 根据 ID 删除-异步
     *
     * @param req 删除数据实体
     * @return 返回成功
     */
    public EsResult deleteAsync(E req) {
        return postBody("/deleteAsync", req,EsResult.class);
    }

    /**
     * 根据 ID 查询单条数据
     *
     * @param req 参数
     * @return 符合条件的数据
     */
    public EsResult<E> getById(E req) {
        Type type = new TypeReference<EsResult<E>>(){}.getType();
        EsResult<E> result = postBody("/getById", req, type);
        this.dataConvert(result);
        return result;
    }

    /**
     * 根据条件查询单条数据
     *
     * @param req 参数
     * @return 符合条件的数据，超过一条则返回失败
     */
    public EsResult<E> getOne(EsBaseParams<E> req) {
        Type type = new TypeReference<EsResult<E>>(){}.getType();
        EsResult<E> result = postBody("/getOne", req, type);
        this.dataConvert(result);
        return result;
    }

    /**
     * 根据条件查询列表
     *
     * @param req 参数
     * @return 符合条件的数据，限制最多1000条返回
     */
    public EsResult<List<E>> getList(EsBaseParams<E> req) {
        Type type = new TypeReference<EsResult<List<E>>>() {}.getType();
        EsResult<List<E>> result = postBody("/getList", req, type);
        this.listConvert(result);
        return result;
    }

    /**
     * 分页查询
     *
     * @param pageReq 分页参数
     * @return 符合条件的数据，分页实体
     */
    public EsResult<EsPageResult<E>> page(EsPageReq<E> pageReq) {
        Type type = new TypeReference<EsResult<EsPageResult<E>>>() {}.getType();
        EsResult<EsPageResult<E>> result = postBody("/page", pageReq, type);
        if (Boolean.TRUE.equals(this.isConvert(result)) && !result.getData().getRecords().isEmpty()) {
            // 做一次实体类型转换
            List<E> data = new ArrayList<>();
            for(E e : result.getData().getRecords()){
                data.add(JSONObject.parseObject(JSONObject.toJSONString(e),this.geteClazz()));
            }
            result.getData().setRecords(data);
        }
        return result;
    }

    /**
     * 根据条件查询总数
     *
     * @param req 参数
     * @return 返回符合条件的总数
     */
    public EsResult<Long> count(EsBaseParams<E> req) {
        Type type = new TypeReference<EsResult<Long>>() {}.getType();
        return postBody("/count", req, type);
    }

    /**
     * 根据字段分组查询
     *
     * @param req 参数
     * @author limaosheng
     * @create 15:53 2024/3/12
     */
    public EsResult<List<GroupByResp>> groupBy(EsBaseParams<E> req) {
        Type type = new TypeReference<EsResult<List<GroupByResp>>>() {}.getType();
        return postBody("/groupBy", req, type);
    }

    /**
     * 分组分页查询
     *
     * @param pageReq 分页参数
     * @return 符合条件的数据，分页实体
     */
    public EsResult<EsPageResult<GroupByResp>> groupByPage(EsPageReq<E> pageReq) {
        Type type = new TypeReference<EsResult<EsPageResult<GroupByResp>>>() {}.getType();
        return postBody("/groupByPage", pageReq, type);
    }

    /**
     * 添加求和统计接口
     * @param req
     * @return
     */
    public EsResult<Double> sum(EsBaseParams<E> req) {
        Type type = new TypeReference<EsResult<Double>>() {}.getType();
        return postBody("/sum", req, type);
    }
}
